use std::{
    error::Error,
    path::{Path, PathBuf},
};

// Keep internal to prevent impls.
use proto_trait::ProtoKind;
// Re-export so callers can see them.
pub use proto_trait::{Bootp, Nfs};

mod nfs {
    #[derive(Clone)]
    // NFS session management omitted.
    #[allow(dead_code)]
    pub(crate) struct AuthInfo(String);
}

mod bootp {
    use crate::{Bootp, FileDownloadRequest};

    // No authentication in bootp.
    pub(crate) struct AuthInfo();

    pub(crate) fn listen() -> Result<AuthInfo, String> {
        Ok(AuthInfo {})
    }

    impl AuthInfo {
        pub(crate) fn next_request(&self) -> Result<Option<FileDownloadRequest<Bootp>>, String> {
            Ok(None)
        }
    }
}

// A private module, lest outside users invent their own protocol kinds!
mod proto_trait {
    use std::path::{Path, PathBuf};

    use super::{bootp, nfs};

    pub(crate) trait ProtoKind {
        type AuthInfo;

        fn auth_info(&self) -> Self::AuthInfo;
    }

    #[allow(dead_code)]
    pub struct Nfs {
        auth: nfs::AuthInfo,
        mount_point: PathBuf,
    }

    impl Nfs {
        pub(crate) fn mount_point(&self) -> &Path {
            &self.mount_point
        }
    }

    impl ProtoKind for Nfs {
        type AuthInfo = nfs::AuthInfo;

        fn auth_info(&self) -> Self::AuthInfo {
            self.auth.clone()
        }
    }

    // No additional metadata.
    pub struct Bootp();

    impl ProtoKind for Bootp {
        type AuthInfo = bootp::AuthInfo;

        fn auth_info(&self) -> Self::AuthInfo {
            bootp::AuthInfo()
        }
    }
}

#[allow(dead_code)]
struct FileDownloadRequest<P: ProtoKind> {
    file_name: PathBuf,
    protocol: P,
}

// All common API parts go into a generic impl block.
impl<P: ProtoKind> FileDownloadRequest<P> {
    #[allow(dead_code)]
    fn file_path(&self) -> &Path {
        &self.file_name
    }

    fn auth_info(&self) -> P::AuthInfo {
        self.protocol.auth_info()
    }
}

// All protocol-specific impls go into their own block.
impl FileDownloadRequest<Nfs> {
    #[allow(dead_code)]
    fn mount_point(&self) -> &Path {
        self.protocol.mount_point()
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let socket = crate::bootp::listen()?;
    while let Some(request) = socket.next_request()? {
        let _info = request.auth_info();
        // Rest of the code here.
    }

    Ok(())
}
